package thesis.javaclass;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import java.io.IOException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import org.xml.sax.SAXException;

public class MapFunction {

	private static final double MIN_LAT = Math.toRadians(-90d); // -PI/2
	private static final double MAX_LAT = Math.toRadians(90d); // PI/2
	private static final double MIN_LON = Math.toRadians(-180d); // -PI
	private static final double MAX_LON = Math.toRadians(180d); // PI
	public static final double RADIUS = 6378.1; // In kilometers

	private static final String GEOCODER_REQUEST_PREFIX_FOR_XML = "http://maps.google.com/maps/api/geocode/xml";
	private static final String DIRECTION_REQUEST_FOR_XML = "http://maps.googleapis.com/maps/api/directions/xml";

	public static ArrayList<Double> getBoundingCoordinates(double latitude,
			double longitude, double distance) {

		// angular distance in radians on a great circle
		double radDist = distance / RADIUS;

		double radianLat = Math.toRadians(latitude);
		double radianLon = Math.toRadians(longitude);

		double minLat = radianLat - radDist;
		double maxLat = radianLat + radDist;

		double minLon, maxLon;
		if (minLat > MIN_LAT && maxLat < MAX_LAT) {
			double deltaLon = Math
					.asin(Math.sin(radDist) / Math.cos(radianLat));
			minLon = radianLon - deltaLon;
			if (minLon < MIN_LON) {
				minLon += 2d * Math.PI;
			}
			maxLon = radianLon + deltaLon;
			if (maxLon > MAX_LON) {
				maxLon -= 2d * Math.PI;
			}
		} else {
			// a pole is within the distance
			minLat = Math.max(minLat, MIN_LAT);
			maxLat = Math.min(maxLat, MAX_LAT);
			minLon = MIN_LON;
			maxLon = MAX_LON;
		}

		ArrayList<Double> boundingCoor = new ArrayList<Double>();

		boundingCoor.add(Math.toDegrees(minLat)); // minLat in degrees
		boundingCoor.add(Math.toDegrees(minLon)); // minLon in degrees
		boundingCoor.add(Math.toDegrees(maxLat)); // maxLat in degrees
		boundingCoor.add(Math.toDegrees(maxLon)); // maxLon in degrees
		return boundingCoor;

	}

	public static ArrayList<Double> getCoordinate(String address)
			throws IOException, SAXException, ParserConfigurationException,
			XPathExpressionException {
		URL url = new URL(GEOCODER_REQUEST_PREFIX_FOR_XML + "?address="
				+ URLEncoder.encode(address, "UTF-8")
				+ "&components=country:VN&ie=UTF-8&sensor=false");

		// prepare an HTTP connection to the geocoder
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();

		Document geocoderResultDocument = null;
		try {
			// open the connection and get results as InputSource.
			conn.connect();
			InputSource geocoderResultInputSource = new InputSource(
					conn.getInputStream());

			// read result and parse into XML Document
			geocoderResultDocument = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder().parse(geocoderResultInputSource);
		} finally {
			conn.disconnect();
		}

		// prepare XPath
		XPath xpath = XPathFactory.newInstance().newXPath();

		// extract the result
		NodeList resultNodeList = null;
		// c) extract the coordinates of the first result
		resultNodeList = (NodeList) xpath.evaluate(
				"/GeocodeResponse/result[1]/geometry/location/*",
				geocoderResultDocument, XPathConstants.NODESET);

		double lat = Double.NaN;
		double lng = Double.NaN;

		for (int i = 0; i < resultNodeList.getLength(); ++i) {
			Node node = resultNodeList.item(i);
			if ("lat".equals(node.getNodeName()))
				lat = Double.parseDouble(node.getTextContent());
			if ("lng".equals(node.getNodeName()))
				lng = Double.parseDouble(node.getTextContent());
		}

		ArrayList<Double> result = new ArrayList<Double>();
		result.add(lat);
		result.add(lng);
		return result;
	}

	public static String getLocation(Double lat, Double lng)
			throws IOException, SAXException, ParserConfigurationException,
			XPathExpressionException {
		URL url = new URL(GEOCODER_REQUEST_PREFIX_FOR_XML + "?latlng=" + lat
				+ "," + lng + "&sensor=false");

		// prepare an HTTP connection to the geocoder
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();

		Document geocoderResultDocument = null;
		try {
			// open the connection and get results as InputSource.
			conn.connect();
			InputSource geocoderResultInputSource = new InputSource(
					conn.getInputStream());

			// read result and parse into XML Document
			geocoderResultDocument = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder().parse(geocoderResultInputSource);
		} finally {
			conn.disconnect();
		}

		// prepare XPath
		XPath xpath = XPathFactory.newInstance().newXPath();

		// extract the result
		NodeList resultNodeList = null;

		// a) obtain the formatted_address field for every result
		resultNodeList = (NodeList) xpath.evaluate(
				"/GeocodeResponse/result/formatted_address",
				geocoderResultDocument, XPathConstants.NODESET);

		return resultNodeList.item(0).getTextContent();
	}

	public static ArrayList<ArrayList<String>> getDirection(String original,
			String destination) throws IOException, SAXException,
			ParserConfigurationException {
		URL url = new URL(DIRECTION_REQUEST_FOR_XML + "?origin="
				+ URLEncoder.encode(original, "UTF-8") + "&destination="
				+ URLEncoder.encode(destination, "UTF-8") + "&sensor=false");

		HttpURLConnection conn = (HttpURLConnection) url.openConnection();

		Document geocoderResultDocument = null;
		try {
			// open the connection and get results as InputSource.
			conn.connect();
			InputSource geocoderResultInputSource = new InputSource(
					conn.getInputStream());

			// read result and parse into XML Document
			geocoderResultDocument = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder().parse(geocoderResultInputSource);
		} finally {
			conn.disconnect();
		}

		geocoderResultDocument.getDocumentElement().normalize();

		NodeList pointList = geocoderResultDocument
				.getElementsByTagName("start_location");

		ArrayList<ArrayList<String>> pointDirections = new ArrayList<ArrayList<String>>();
		pointDirections.add(new ArrayList<String>());
		pointDirections.add(new ArrayList<String>());

		for (int i = 0; i < pointList.getLength() - 1; i++) {
			Node firstNode = pointList.item(i);
			if (firstNode.getNodeType() == Node.ELEMENT_NODE) {
				Element firstPointElement = (Element) firstNode;

				NodeList latList = firstPointElement
						.getElementsByTagName("lat");
				Element latElement = (Element) latList.item(0);

				NodeList textLatList = latElement.getChildNodes();

				System.out.print("Lat: "
						+ ((Node) textLatList.item(0)).getNodeValue().trim());

				NodeList lngList = firstPointElement
						.getElementsByTagName("lng");
				Element lngElement = (Element) lngList.item(0);

				NodeList textLngList = lngElement.getChildNodes();

				System.out.println(", "
						+ ((Node) textLngList.item(0)).getNodeValue().trim());

				pointDirections.get(0).add(
						((Node) textLatList.item(0)).getNodeValue().trim());
				pointDirections.get(1).add(
						((Node) textLngList.item(0)).getNodeValue().trim());
			}
		}

		NodeList endPointList = geocoderResultDocument
				.getElementsByTagName("end_location");

		Node firstNode = endPointList.item(endPointList.getLength() - 1);

		if (firstNode.getNodeType() == Node.ELEMENT_NODE) {
			Element firstPointElement = (Element) firstNode;

			NodeList latList = firstPointElement.getElementsByTagName("lat");
			Element latElement = (Element) latList.item(0);

			NodeList textLatList = latElement.getChildNodes();

			System.out.print("Lat: "
					+ ((Node) textLatList.item(0)).getNodeValue().trim());

			NodeList lngList = firstPointElement.getElementsByTagName("lng");
			Element lngElement = (Element) lngList.item(0);

			NodeList textLngList = lngElement.getChildNodes();

			System.out.println(", "
					+ ((Node) textLngList.item(0)).getNodeValue().trim());

			pointDirections.get(0).add(
					((Node) textLatList.item(0)).getNodeValue().trim());
			pointDirections.get(1).add(
					((Node) textLngList.item(0)).getNodeValue().trim());
		}

		return pointDirections;
	}

	public static double getDistance(String original, String destination)
			throws IOException, SAXException, ParserConfigurationException {
		URL url = new URL(DIRECTION_REQUEST_FOR_XML + "?origin="
				+ URLEncoder.encode(original, "UTF-8") + "&destination="
				+ URLEncoder.encode(destination, "UTF-8") + "&sensor=false");

		HttpURLConnection conn = (HttpURLConnection) url.openConnection();

		Document geocoderResultDocument = null;
		try {
			// open the connection and get results as InputSource.
			conn.connect();
			InputSource geocoderResultInputSource = new InputSource(
					conn.getInputStream());

			// read result and parse into XML Document
			geocoderResultDocument = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder().parse(geocoderResultInputSource);
		} finally {
			conn.disconnect();
		}

		geocoderResultDocument.getDocumentElement().normalize();
		double distance = 0.0;

		NodeList list = geocoderResultDocument.getElementsByTagName("distance");
		Node firstNode = list.item(list.getLength() - 1);
		if (firstNode.getNodeType() == Node.ELEMENT_NODE) {
			Element firstNodeElement = (Element) firstNode;
			NodeList distanceList = firstNodeElement
					.getElementsByTagName("value");

			Element valueElement = (Element) distanceList.item(0);

			NodeList textValue = valueElement.getChildNodes();

			try {
				System.out.println(textValue.item(0).getNodeValue());
				distance += Integer.parseInt(textValue.item(0).getNodeValue());
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		geocoderResultDocument = null;

		return distance / 1000;
	}

	public static ArrayList<Double> getZoomLevel(ArrayList<Double> latList,
			ArrayList<Double> lngList) {

		double maxLat = Collections.max(latList);
		double minLat = Collections.min(latList);
		double maxLng = Collections.max(lngList);
		double minLng = Collections.min(lngList);
		double ctrLat = (minLat + maxLat) / 2;
		double ctrLng = (minLng + maxLng) / 2;
		double mapDisplay = 600;
		double interval = 0;

		if ((maxLat - minLat) > (maxLng - minLng)) {
			interval = (maxLat - minLat) / 2;
			minLng = ctrLng - interval;
			maxLng = ctrLng + interval;
		} else {
			interval = (maxLng - minLng) / 2;
			minLat = ctrLat - interval;
			maxLat = ctrLat + interval;
		}

		double dist = (6371 * Math
				.acos(Math.sin(minLat / 57.2958)
						* Math.sin(maxLat / 57.2958)
						+ (Math.cos(minLat / 57.2958)
								* Math.cos(maxLat / 57.2958) * Math
									.cos((maxLng / 57.2958)
											- (minLng / 57.2958)))));

		double zoomLevel = (int) Math.floor(8
				- Math.log(1.6446 * dist
						/ Math.sqrt(2 * (mapDisplay * mapDisplay)))
				/ Math.log(2));

		ArrayList<Double> results = new ArrayList<Double>();
		results.add(zoomLevel);
		results.add(ctrLat);
		results.add(ctrLng);

		return results;
	}
}
